package com.egg.eggdemo;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Transaction;

import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.*;

@Slf4j
public class EggBreakService {
    private static final String COUPON_KEY = "egg_break:coupons";  // 存储奖券库存
    private static final String PROB_KEY = "egg_break:probabilities";  // 存储奖券概率
    private static final String DAY_KEY = "egg_break:current_day";  // 当前活动天数
    private static final String USER_PREFIX = "egg_break:user:";  // 用户数据前缀
    private static final String LOCK_PREFIX = "egg_break:lock:";  // 锁前缀
    private static final String DAILY_INIT_KEY = "egg_break:daily_init"; // 新增

    private final JedisPool jedisPool;
    private final RedisLockUtil lockUtil;

    public EggBreakService() {
        // 配置连接池
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(10);  // 最大连接数
        this.jedisPool = new JedisPool(poolConfig, "localhost", 6379);
        this.lockUtil = new RedisLockUtil(jedisPool);
    }

    // 用户砸蛋方法
    public String breakEgg(String userId) {
        // 生成requestId用于锁标识，确保锁的唯一性
        String requestId = UUID.randomUUID().toString();
        // 用户级锁key: Key格式：egg_break:lock:user:userId
        String userLockKey = LOCK_PREFIX + "user:" + userId;
        // 库存锁key: Key格式：egg_break:lock:stock
        String stockLockKey = LOCK_PREFIX + "stock";

        try (Jedis jedis = jedisPool.getResource()) {

            // 1. 获取用户锁(防止同一用户并发),设置3秒过期时间，最多等待1秒; 获取失败说明用户操作太频繁
            if (!lockUtil.tryLock(userLockKey, requestId, 3, 1000)) {
                return "操作太频繁，请稍后再试";
            }
            try {
                // 获取当前日期（yyyy-MM-dd格式）
                String today = LocalDate.now().toString();
                // 1.1获取当前活动天数
                String currentDayStr = jedis.get(DAY_KEY);
                if (currentDayStr == null) {
                    return "活动未开始";
                }
                // 1.2 获取当前活动天数
                int currentDay = Integer.parseInt(jedis.get(DAY_KEY));
                if (currentDay > 7) {
                    return "活动已结束";
                }

                // 2. 检查用户是否已参与当前天的活动
                String userKey = USER_PREFIX + userId;
                String dayField = "day" + currentDay; // （如day1/day2/day3...）
                if (jedis.hexists(userKey, dayField)) { // hexists命令：检查Hash中是否存在指定字段:如果已有day3字段 → 今天已玩过 → 拒绝
                    return "今日已砸蛋，请明天再来";
                }
                // 新增：获取库存锁
                if (!lockUtil.tryLock(stockLockKey, requestId, 3, 1000)) {
                    return "系统繁忙，请稍后再试";
                }


                // 新检查3：（可选）防止日期错乱
                String lastDate = jedis.hget(userKey, "last_break_time");
                if (lastDate != null && !isConsecutiveDay(lastDate, today)) {
                    return "请按顺序参与活动，您上次参与是" + lastDate;
                }

                try {
                    // 3. 获取可用奖券及概率
                    Map<String, String> couponMap = jedis.hgetAll(COUPON_KEY);
                    Map<String, String> probMap = jedis.hgetAll(PROB_KEY);

//                    // 4. 过滤掉数量为0的奖券
//                    Map<String, Double> available = new HashMap<>();
//                    for (Map.Entry<String, String> entry : couponMap.entrySet()) {
//                        int count = Integer.parseInt(entry.getValue());
//                        if (count > 0) {
//                            available.put(entry.getKey(), Double.parseDouble(probMap.get(entry.getKey())));
//                        }
//                    }
//                    if (available.isEmpty()) {
//                        return "所有奖券已抽完";
//                    }

                    // 4. 过滤库存为0的奖券---只保留库存>0的奖券
                    Map<String, Double> available = filterAvailableCoupons(couponMap, probMap);
//                    if (available.isEmpty()) {
//                        return "所有奖券已抽完";
//                    }

                    // 5. 按概率随机选择奖券----使用轮盘赌算法随机选择
                    String selected = selectCoupon(available);

                    // 新增：处理未中奖情况
                    if ("NONE".equals(selected)) {
                        // 记录用户已参与，但不扣减奖券
                        Transaction transaction = jedis.multi();
                        transaction.hset(userKey, dayField, "NONE");
                        transaction.hset(userKey, "last_break_time", LocalDate.now().toString());
                        List<Object> results = transaction.exec();

                        if (results == null || results.isEmpty()) {
                            return "抽奖失败，请重试";
                        }
                        return "很遗憾，未中奖，明天再来吧！";
                    }

                    // 6. 中奖情况：扣减奖券数量(使用事务保证原子性)
                    jedis.watch(COUPON_KEY);  // 监控键(监视库存变化)，如果其他客户端修改了监控的key，事务会失败
                    Transaction transaction = jedis.multi();  // 获取事务对象---multi开始事务
                    transaction.hincrBy(COUPON_KEY, selected, -1);    // 扣库存
                    transaction.hset(userKey, "day" + currentDay, selected);   // 记录中奖到当前天的字段
                    transaction.hset(userKey, "last_break_time", LocalDate.now().toString());  // 更新最后参与时间
                    List<Object> results = transaction.exec();  // 执行事务----exec提交事务
                    if (results == null || results.isEmpty()) {
                        return "抽奖失败，请重试";  // 事务失败，说明数据被其他线程修改
                    }
                    return "恭喜获得 " + selected + " 元奖券！";
                } finally {
                    // 释放库存锁
                    lockUtil.releaseLock(stockLockKey, requestId);
                }
            } finally {
                // 释放用户锁
                lockUtil.releaseLock(userLockKey, requestId);
            }
        } catch (Exception e) {
            log.error("砸蛋异常 userId:{}", userId, e);
            return "系统繁忙，请稍后再试";
        }
    }

    // 新增：提取过滤可用奖券的方法(将有库存的奖券和对应概率组合起来)
    private Map<String, Double> filterAvailableCoupons(Map<String, String> couponMap, Map<String, String> probMap) { Map<String, Double> available = new HashMap<>();
        for (Map.Entry<String, String> entry : couponMap.entrySet()) {
            int count = Integer.parseInt(entry.getValue());
            if (count > 0) {
                available.put(entry.getKey(), Double.parseDouble(probMap.get(entry.getKey())));
            }
        }
        return available;
    }


    // 按概率选择奖券(经典的轮盘赌算法实现概率选择)
    private String selectCoupon(Map<String, Double> available) {
        double totalProb = available.values().stream().mapToDouble(Double::doubleValue).sum();
//        double rand = new Random().nextDouble() * totalProb;
        double rand = new Random().nextDouble();  // 新增：生成0-1之间的随机数

        // 新增：如果所有奖券概率之和小于随机数，表示未中奖
        if (rand > totalProb) {
            return "NONE"; // 表示未中奖
        }

//        for (Map.Entry<String, Double> entry : available.entrySet()) {
//            cumulative += entry.getValue();
//            if (rand <= cumulative) {
//                return entry.getKey();
//            }
//        }
//        return available.keySet().iterator().next();  // 理论上不会执行到这里

        double cumulative = 0.0;
        for (Map.Entry<String, Double> entry : available.entrySet()) {
            cumulative += entry.getValue();
            if (rand <= cumulative) {
                return entry.getKey();
            }
        } return "NONE"; // 未中奖

    }


    // 每日重置
    public void dailyReset() {
        try (Jedis jedis = jedisPool.getResource()) {
//            // 只增加天数，不再清除用户记录
//            jedis.incr(DAY_KEY);  // 天数+1
//            log.info("活动天数已更新，当前为第{}天", jedis.get(DAY_KEY));

            // 1. 获取每日初始数量
            Map<String, String> dailyInit = jedis.hgetAll(DAILY_INIT_KEY);

            // 2. 重置当前奖券数量为初始值
            Transaction transaction = jedis.multi();
            for (Map.Entry<String, String> entry : dailyInit.entrySet()) {
                transaction.hset(COUPON_KEY, entry.getKey(), entry.getValue());
            }

            // 3. 增加天数
            transaction.incr(DAY_KEY);

            List<Object> results = transaction.exec();
            if (results != null && !results.isEmpty()) {
                log.info("活动天数已更新，当前为第{}天", jedis.get(DAY_KEY));
                log.info("奖券数量已重置: {}", jedis.hgetAll(COUPON_KEY));
            }

        }
    }

    // 关闭连接池
    public void close() {
        jedisPool.close();
    }

    /**
     * 检查上次参与日期是否是今天的前一天（确保连续参与）
     * @param lastDateStr 上次参与日期（格式yyyy-MM-dd）
     * @param todayStr 今天日期（格式yyyy-MM-dd）
     * @return 如果是连续天数返回true
     */
    private boolean isConsecutiveDay(String lastDateStr, String todayStr) {
        try {
            LocalDate lastDate = LocalDate.parse(lastDateStr);
            LocalDate today = LocalDate.parse(todayStr);

            // 检查是否是前一天（允许跨月/跨年）
            return lastDate.plusDays(1).equals(today);
        } catch (Exception e) {
            log.error("日期解析失败 last:{} today:{}\\", lastDateStr, todayStr, e);
            return false;
        }
    }
}
